﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml.Serialization;
using NPlot;
using SleepMonitor.App.Business;
using SleepMonitor.App.UI.Properties;
using File=SleepMonitor.App.Business.File;
using PlotSurface2D=NPlot.Windows.PlotSurface2D;
using Point=SleepMonitor.App.Business.Point;

namespace SleepMonitor.App.UI
{
	public partial class MainForm : Form
	{
		private const int MAX_ZOOM_FACTOR = 10;
		private readonly List<PlotSurface2D> surfaces = new List<PlotSurface2D>();
		private Cursor previousCursor = null;
		private File file;
		private FileManager fileManager = new FileManager();
		private string currentFileName;

		public MainForm()
		{
			InitializeComponent();
		}

		public string CurrentFileName
		{
			get { return currentFileName; }
			set { currentFileName = value; }
		}

		private void Form1_Load(object sender, EventArgs e)
		{
			layoutPanel.Visible = false;
			toolStrip2.Visible = false;

			if (!string.IsNullOrEmpty(CurrentFileName))
			{
				OpenFile(CurrentFileName);
			}
		}


		void plotSurface2D_InteractionOccured(object sender)
		{
			var drag = sender as IInteraction;

			if (drag != null)
			{
				foreach (var surface in surfaces)
				{
					if (surface != drag.Control)
					{
						surface.XAxis1 = drag.Control.XAxis1;
						surface.Refresh();
					}
				}				
			}
		}

		private IEnumerable<Point> NormalizePoints(Sensor sensor)
		{
			List<Point> points = sensor.Points;

			double mediumPoint = GetMediumPoint(points);

			int count = points.Count();
			double maxWidth = layoutPanel.Width * MAX_ZOOM_FACTOR;

			int partSize = count / (int)maxWidth;

			double biggestDeviation = 0;

			Point pointToPlot = null;
			Point point;

			for (int i = 0; i < count; i++)
			{
				point = points[i];

				if (pointToPlot == null)
				{
					pointToPlot = point;
				}

				double deviation = Math.Abs(point.Y - mediumPoint);

				if (deviation > biggestDeviation)
				{
					biggestDeviation = deviation;
					pointToPlot = point;
				}

				if (i % partSize == 0)
				{
					yield return pointToPlot;
					pointToPlot = null;
					biggestDeviation = 0;
				}
			}
		}

		private static double GetMediumPoint(IEnumerable<Point> points)
		{
			double greater = double.MinValue;
			double smaller = double.MaxValue;

			foreach (var currentPoint in points)
			{
				if (currentPoint.Y > greater)
				{
					greater = currentPoint.Y;
				}
				if (currentPoint.Y < smaller)
				{
					smaller = currentPoint.Y;
				}
			}

			return smaller + greater / 2;
		}


		private void importToolStripMenuItem_Click(object sender, EventArgs e)
		{
			Import();
		}

		private void PlotSensors(IList<Sensor> sensors)
		{
			layoutPanel.Visible = true;

            initialDateContent.Text = sensors[0].Points[0].X.Date.ToString().Substring(0,10);
            finalDateContent.Text = sensors[0].Points[sensors.Count - 1].X.Date.ToString().Substring(0,10);
            initialTimeContent.Text = sensors[0].Points[0].X.TimeOfDay.ToString().Substring(0,12);
            finalTimeContent.Text = sensors[0].Points[sensors.Count - 1].X.TimeOfDay.ToString().Substring(0,12);

			var horizontalDrag = new HorizontalDrag(this);
			var verticalDrag = new VerticalDrag(this);
			var selection = new Selection(this);


			SplitContainer currentLayoutPanel = layoutPanel;

			for (int i = 0; i < sensors.Count; i++)
			{
				currentLayoutPanel.SplitterDistance = (ClientRectangle.Height / (sensors.Count)) - (sensors.Count * currentLayoutPanel.SplitterWidth);

				var plotSurface2D = new PlotSurface2D();

				var plot = new LinePlot();

                var normalizedPoints = NormalizePoints(sensors[i]).ToList();

				plot.AbscissaData = normalizedPoints.Select(p => p.X).ToArray();
				plot.OrdinateData = normalizedPoints.Select(p => p.Y).ToArray();

				plotSurface2D.Add(plot);
				plotSurface2D.AddInteraction(horizontalDrag);
				plotSurface2D.AddInteraction(verticalDrag);
				plotSurface2D.AddInteraction(selection);

				plotSurface2D.InteractionOccured += plotSurface2D_InteractionOccured;

				plotSurface2D.MouseWheel += plotSurface2D_MouseWheel;
				plotSurface2D.MouseHover += plotSurface2D_MouseHover;

				plotSurface2D.ShowCoordinates = false;

                plotSurface2D.XAxis1.TicksIndependentOfPhysicalExtent = true;
                plotSurface2D.XAxis1.TickTextNextToAxis = false;
                plotSurface2D.XAxis1.TicksAngle = (float)Math.PI / 2.0f;

                plotSurface2D.XAxis2 = (Axis)plotSurface2D.XAxis1.Clone();
                plotSurface2D.XAxis2.HideTickText = true;
                plotSurface2D.XAxis2.LargeTickSize = 0;

                plotSurface2D.YAxis1.HideTickText = true;
                plotSurface2D.YAxis1.LargeTickSize = 0;
                plotSurface2D.YAxis1.SmallTickSize = 0;

                plotSurface2D.YAxis2 = (Axis)plotSurface2D.YAxis1.Clone();
				plotSurface2D.Dock = DockStyle.Fill;

				if (i < sensors.Count - 2)
				{
					currentLayoutPanel.Panel1.Controls.Add(plotSurface2D);

					SplitContainer newContainer = new SplitContainer()
					                              	{
					                              		Orientation = Orientation.Horizontal, 
														Dock = DockStyle.Fill, 
														BorderStyle = BorderStyle.Fixed3D
					                              	};
					currentLayoutPanel.Panel2.Controls.Add(newContainer);
					currentLayoutPanel = newContainer;
				}
				else if (i == sensors.Count - 2)
				{
					currentLayoutPanel.Panel1.Controls.Add(plotSurface2D);
				}
				else
				{
					currentLayoutPanel.Panel2.Controls.Add(plotSurface2D);
				}

				surfaces.Add(plotSurface2D);
			}

			moveToolStripMenuItem.Enabled = moveButton.Enabled = true;
			zoomToolStripMenuItem.Enabled = zoomButton.Enabled = true;
			markToolStripMenuItem.Enabled = markButton.Enabled = true;
			saveButton.Enabled = true;
			saveToolStripMenuItem.Enabled = true;
            toolStrip2.Visible = true;
		}

		void plotSurface2D_MouseHover(object sender, EventArgs e)
		{
			if (zoomButton.Checked || markButton.Checked)
			{
				((Control) sender).Focus();
			}
		}

		void plotSurface2D_MouseWheel(object sender, MouseEventArgs e)
		{
			double zoomFactor = -0.005;

			zoomFactor *= (e.Delta > 0) ? 1 : -1;
			
			if (zoomButton.Checked)
			{
				foreach (var surface in surfaces)
				{
					double aspectRatio = (double) surface.Width/surface.Height;
					surface.XAxis1.IncreaseRange(zoomFactor*aspectRatio);
					surface.YAxis1.IncreaseRange(zoomFactor*aspectRatio);
					surface.Refresh();
				}
			}
		}


		/// <summary>
		/// Handles the Click event of the zoomButton control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void zoomButton_Click(object sender, EventArgs e)
		{
			EnableZoom();
		}

		/// <summary>
		/// Enables the zoom.
		/// </summary>
		private void EnableZoom()
		{
			markToolStripMenuItem.Checked = markButton.Checked = false;
			moveToolStripMenuItem.Checked = moveButton.Checked = false;
			zoomToolStripMenuItem.Checked = zoomButton.Checked = true;

			foreach (var surface in surfaces)
			{
				surface.Cursor = (zoomButton.Checked) ? new Cursor(Resources.MagnifierLens.ToBitmap().GetHicon()) : Cursors.Default;
			}
		}

		/// <summary>
		/// Handles the Click event of the moveButton control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void moveButton_Click(object sender, EventArgs e)
		{
			EnableMove();
		}

		/// <summary>
		/// Enables the move.
		/// </summary>
		private void EnableMove()
		{
			markToolStripMenuItem.Checked = markButton.Checked = false;
			moveToolStripMenuItem.Checked = moveButton.Checked = true;
			zoomToolStripMenuItem.Checked = zoomButton.Checked = false;

			foreach (var surface in surfaces)
			{
				surface.Cursor = (moveButton.Checked) ? Cursors.SizeAll : Cursors.Default;
			}
		}

		/// <summary>
		/// Handles the Click event of the markButton control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void markButton_Click(object sender, EventArgs e)
		{
			EnableMark();
		}

		/// <summary>
		/// Enables the mark.
		/// </summary>
		private void EnableMark()
		{
			markToolStripMenuItem.Checked = markButton.Checked = true;
			moveToolStripMenuItem.Checked = moveButton.Checked = false;
			zoomToolStripMenuItem.Checked = zoomButton.Checked = false;

			foreach (var surface in surfaces)
			{
				surface.Cursor = (markButton.Checked) ? Cursors.IBeam : Cursors.Default;
			}
		}

		private void zoomToolStripMenuItem_Click(object sender, EventArgs e)
		{
			EnableZoom();
		}

		private void moveToolStripMenuItem_Click(object sender, EventArgs e)
		{
			EnableMove();
		}

		private void markToolStripMenuItem_Click(object sender, EventArgs e)
		{
			EnableMark();
		}

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

		private void toolStripButton3_Click(object sender, EventArgs e)
		{
			Import();
		}


		/// <summary>
		/// Imports the data that will be used
		/// </summary>
		private void Import()
		{
			var openFileDialog = new OpenFileDialog
			{
				Filter = "Data Sources (*.txt)|*.txt;|All Files|*.*"
			};

			if (openFileDialog.ShowDialog() == DialogResult.OK && openFileDialog.CheckFileExists)
			{
				this.Cursor = Cursors.WaitCursor;
				this.file = fileManager.ImportTextFile(openFileDialog.FileName);
				CurrentFileName = null;

				PlotSensors(file.Sensors);
				this.Cursor = Cursors.Default;
			}
		}

		/// <summary>
		/// Saves the current data into a file.
		/// </summary>
		private void Save()
		{
			if (string.IsNullOrEmpty(CurrentFileName))
			{
				var saveFileDialog = new SaveFileDialog
             	{
             		Filter = "Sleep Monitor Data File (*.smdf)|*.smdf"
             	};

				if (saveFileDialog.ShowDialog() == DialogResult.OK)
				{
					CurrentFileName = saveFileDialog.FileName;
				}
			}
			this.Cursor = Cursors.WaitCursor;
			fileManager.Save(file, CurrentFileName);
			this.Cursor = Cursors.Default;
		}

		/// <summary>
		/// Opens a file.
		/// </summary>
		private void Open()
		{
			var openFileDialog = new OpenFileDialog
			{
				Filter = "Sleep Monitor Data File (*.smdf)|*.smdf"
			};

			if (openFileDialog.ShowDialog() == DialogResult.OK && openFileDialog.CheckFileExists)
			{
				CurrentFileName = openFileDialog.FileName;
				OpenFile(CurrentFileName);
			}
		}


		/// <summary>
		/// Opens the file.
		/// </summary>
		/// <param name="fileName">Name of the file.</param>
		private void OpenFile(string fileName)
		{
			this.Cursor = Cursors.WaitCursor;
			this.file = fileManager.Open(CurrentFileName);

			PlotSensors(file.Sensors);
			this.Cursor = Cursors.Default;
		}

		/// <summary>
		/// Handles the Click event of the saveButton control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void saveButton_Click(object sender, EventArgs e)
		{
			Save();
		}

		/// <summary>
		/// Handles the Click event of the toolStripButton2 control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void toolStripButton2_Click(object sender, EventArgs e)
		{
			Open();
		}

		private void toolStripMenuItem2_Click(object sender, EventArgs e)
		{
			Open();
		}

		private void toolStripMenuItem3_Click(object sender, EventArgs e)
		{
			Save();
		}
	}
}
